// Copyright 2020 Bytedance Inc. All Rights Reserved.
// Author: fengkai.0518@bytedance.com (fengkai.0518)

package com.bytedance.speech.speechdemo;

import android.Manifest;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;

import androidx.lifecycle.LifecycleObserver;

import com.bytedance.speech.speechdemo.settings.Settings;
import com.bytedance.speech.speechdemo.utils.SpeechDemoDefines;
import com.bytedance.speech.speechdemo.utils.SpeechFileRecorder;
import com.bytedance.speech.speechdemo.utils.SpeechFileUtils;
import com.bytedance.speech.speechengine.SpeechEngine;
import com.bytedance.speech.speechengine.SpeechEngineDefines;
import com.bytedance.speech.speechengine.SpeechEngineGenerator;

import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;

public class AfpActivity extends BaseActivity implements LifecycleObserver {


    // FileRecorder
    private SpeechFileRecorder mFileRecorder;

    // Engine
    private SpeechEngine mSpeechEngine = null;
    private long mSpeechEngineHandler = -1;
    private boolean mEngineStarted = false;

    // UI
    private TextView mResult;
    private TextView mEngineStatus;
    private Button mInit;
    private Button mUninit;
    private Button mStart;
    private Button mStop;
    private Button mFetchResult;

    // Permissions
    private static final List<String> AFP_PERMISSIONS = Arrays.asList(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.RECORD_AUDIO
    );

    // Settings
    protected Settings mSettings;

    @SuppressLint({"ClickableViewAccessibility", "InflateParams", "HardwareIds"})
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i(SpeechDemoDefines.TAG, "Afp onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_afp);
        setTitleBar(R.string.afp_name);
        requestPermission(AFP_PERMISSIONS);

        final String viewId = SpeechDemoDefines.AFP_VIEW;

        mSettings = SettingsActivity.getSettings(viewId);
        mFileRecorder = new SpeechFileRecorder();

        mResult = findViewById(R.id.result_text);
        mResult.setMovementMethod(new ScrollingMovementMethod());
        mResult.setText("");

        mEngineStatus = findViewById(R.id.engine_status);
        mEngineStatus.setText(R.string.hint_waiting_init);

        Button mConfig = findViewById(R.id.engine_config);
        mConfig.setEnabled(true);
        mConfig.setOnClickListener(v -> goToSettingsActivity(viewId));

        mInit = findViewById(R.id.init_engine_button);
        setButton(mInit, true);
        mInit.setOnClickListener(v -> init());

        mUninit = findViewById(R.id.uninit_engine_button);
        setButton(mUninit, false);
        mUninit.setOnClickListener(v -> uninit());

        mStart = findViewById(R.id.start_engin_button);
        setButton(mStart, false);
        mStart.setOnClickListener(v -> startEngine());

        mStop = findViewById(R.id.stop_button);
        setButton(mStop, false);
        mStop.setOnClickListener(v -> stopEngine());

        mFetchResult = findViewById(R.id.fetch_result);
        setButton(mFetchResult, false);
        mFetchResult.setOnClickListener(v -> fetchResult());
    }

    @Override
    protected void onDestroy() {
        Log.i(SpeechDemoDefines.TAG, "Afp onDestroy");
        if (mSpeechEngine != null) {
            mSpeechEngine.destroyEngine();
            mSpeechEngine = null;
        }
        super.onDestroy();
    }

    private void init() {
        if (mEngineStarted) {
            mEngineStatus.setText(R.string.hint_engine_busy);
            return;
        }
        setButton(mStart, false);
        setButton(mStop, false);
        setButton(mFetchResult, false);
        setupEngine();
    }

    private void uninit() {
        if (mEngineStarted) {
            mEngineStatus.setText(R.string.hint_engine_busy);
            return;
        }
        uninitEngine();
        mEngineStatus.setText(R.string.hint_waiting_init);
        mResult.setText("");

        setButton(mUninit, false);
        setButton(mInit, true);
        setButton(mStart, false);
        setButton(mStop, false);
        setButton(mFetchResult, false);
    }

    private void startEngine() {
        Log.i(SpeechDemoDefines.TAG, "Speech engine start to feed.");
        if (!mFileRecorder.Start(getDebugPath(), "test_afp.pcm")) {
            mEngineStatus.setText("Start failed, test_afp.pcm not found!");
            return;
        }
        mEngineStarted = true;
        mEngineStatus.setText(R.string.hint_start_cb);
        setButton(mStart, false);
        setButton(mStop, true);
        setButton(mFetchResult, true);
        clearResultText();
    }

    private void stopEngine() {
        mFileRecorder.Stop();
        int ret = mSpeechEngine.resetEngine(mSpeechEngineHandler);
        Log.i(SpeechDemoDefines.TAG, "Speech engine reset ret: " + ret);
        mEngineStarted = false;
        mEngineStatus.setText(R.string.hint_stop_cb);
        setButton(mStart, true);
        setButton(mStop, false);
        setButton(mFetchResult, false);
    }

    private void fetchResult() {
        byte[] result = new byte[4096 * 3];
        int ret = mSpeechEngine.fetchResult(mSpeechEngineHandler, result);
        Log.i(SpeechDemoDefines.TAG, "Speech engine fetch result ret: " + ret);
        setResultText("result length: " + ret);
        if (ret > 0) {
            OutputStream out = SpeechFileUtils.OpenOutputFile(getDebugPath(), "test_afp_out.log");
            SpeechFileUtils.WriteData(out, result, ret);
            SpeechFileUtils.CloseOutputFile(out);
        }
    }

    public void speechEnginInitOk(long initCost) {
        Log.i(SpeechDemoDefines.TAG, "Speech engine init OK!");
        mFileRecorder.SetSpeechEngine(mSpeechEngine, mSpeechEngineHandler);
        this.runOnUiThread(() -> {
            setResultText("Cost: " + initCost);
            mEngineStatus.setText(R.string.hint_ready);
            setButton(mInit, false);
            setButton(mUninit, true);
            setButton(mStart, true);
        });
    }

    public void speechEngineInitFailed(String tipText) {
        Log.e(SpeechDemoDefines.TAG, "Speech engine init failed!");
        this.runOnUiThread(() -> {
            mEngineStatus.setText(R.string.hint_setup_failure);
            setButton(mInit, true);
            setResultText(tipText);
        });
    }

    public void setResultText(final String text) {
        mResult.setText("");
        mResult.append("\n" + text);
    }

    public void clearResultText() {
        this.runOnUiThread(() -> mResult.setText(""));
    }

    private void setupEngine() {
        initEngine();
    }

    private void configAfpParams() {
        // common
        Log.d(SpeechDemoDefines.TAG, "Debug path:" + getDebugPath());

        mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_ENGINE_NAME_STRING, SpeechEngineDefines.AFP_ENGINE);
        mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_LOG_LEVEL_STRING, SpeechEngineDefines.LOG_LEVEL_TRACE);
        mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_DEBUG_PATH_STRING, getDebugPath());
    }

    private void initEngine() {
        Log.d(SpeechDemoDefines.TAG, "Speech engine handler = " + mSpeechEngineHandler);
        if (mSpeechEngineHandler == -1) {

            mSpeechEngine = SpeechEngineGenerator.getInstance();
            mSpeechEngineHandler = mSpeechEngine.createEngine();
        }
        Log.d(SpeechDemoDefines.TAG, "SDK version: " + mSpeechEngine.getVersion());

        configAfpParams();
        long startInitTimestamp = System.currentTimeMillis();
        int ret = mSpeechEngine.initEngine();
        if (ret != SpeechEngineDefines.ERR_NO_ERROR) {
            String errMessage = "Init Engine Faile: " + ret;
            Log.e(SpeechDemoDefines.TAG, errMessage);
            speechEngineInitFailed(errMessage);
            return;
        }
        long cost = System.currentTimeMillis() - startInitTimestamp;
        speechEnginInitOk(cost);
    }

    private void uninitEngine() {
        if (mSpeechEngineHandler != -1) {
            mSpeechEngine.destroyEngine();
            mSpeechEngineHandler = -1;
            mSpeechEngine = null;
            Log.d(SpeechDemoDefines.TAG, "Speech engine uninit Ok!");
        }
    }
}
